package be.kuleuven.cs.mop.app.parser;


import static be.kuleuven.cs.mop.app.parser.ParserUtils.*;
import static be.kuleuven.cs.mop.util.TestUtils.print;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;

import be.kuleuven.cs.mop.app.controllers.Controllers;
import be.kuleuven.cs.mop.app.controllers.ProjectController;
import be.kuleuven.cs.mop.app.controllers.ResourceController;
import be.kuleuven.cs.mop.app.controllers.TaskController;
import be.kuleuven.cs.mop.app.controllers.UserController;
import be.kuleuven.cs.mop.domain.exceptions.InvalidCurrentUserException;
import be.kuleuven.cs.mop.domain.exceptions.TaskManagerException;
import be.kuleuven.cs.mop.domain.model.Project;
import be.kuleuven.cs.mop.domain.model.Resource;
import be.kuleuven.cs.mop.domain.model.Task;
import be.kuleuven.cs.mop.domain.model.TaskStatus;
import be.kuleuven.cs.mop.domain.model.TaskType;
import be.kuleuven.cs.mop.domain.model.Theme;
import be.kuleuven.cs.mop.domain.model.User;
import be.kuleuven.cs.mop.domain.model.impl.Field;
import be.kuleuven.cs.mop.domain.model.impl.FieldType;
import be.kuleuven.cs.mop.domain.model.impl.ReservationImpl;
import be.kuleuven.cs.mop.domain.model.impl.Schedule;
import be.kuleuven.cs.mop.domain.model.impl.TaskImpl;
import be.kuleuven.cs.mop.domain.model.impl.TaskTypeImpl;
import be.kuleuven.cs.mop.domain.model.impl.UserImpl;



/**
 * This class is capable of performing magic
 */
public class DataParser {
	
	private final Cache cache = new Cache();
	
	
	
	private final Cache getCache() { return cache; }
	
	private final TaskStatus getTaskStatus(final String input) {
		if ((input != null) && !input.isEmpty()) {
			if (input.equalsIgnoreCase("failed"))
				return TaskStatus.FAILED;
			else if (input.equalsIgnoreCase("successful"))
				return TaskStatus.SUCCESSFUL;
			else if (input.equalsIgnoreCase("unfinished"))
				return TaskStatus.UNFINISHED;
		}
		throw new IllegalArgumentException("Invalid input:  " + input);
	}
	
	@SuppressWarnings("unchecked")
	public void parse(final File file, final Controllers controllers,
			final Theme theme) throws IOException, JDOMException {
		if (file == null)
			throw new NullPointerException("File == NULL");
		if (theme == null)
			throw new NullPointerException("Theme == NULL");
		if (controllers == null)
			throw new NullPointerException("Controllers == NULL");
		
		final Document	doc		= ParserUtils.parse(file);
		final Element	root	= doc.getRootElement();
		if (!root.getName().equalsIgnoreCase("TaskPlanner"))
			throw new IllegalArgumentException("Invalid XML syntax");
		
		final ProjectController		projects	= controllers.getControllerProjects();
		final ResourceController	resources	= controllers.getControllerResources();
		final TaskController		tasks		= controllers.getControllerTasks();
		final UserController		users		= controllers.getControllerUsers();
		
		
		try {
			final User admin = users.getCurrentLoggedInUser();
			
			
			//	System time
			final Calendar time = parseSystemTime(getChild(root, "SystemTime"));
			
			
			//	Projects
			for (final Element project : getNodes(root, "projects"))
				parseProject(project, projects);
			
			
			//	Resources
			for (final Element resource : getNodes(root, "resources"))
				parseResource(resource, resources, theme);
			
			
			//	Everything else combined in a horribly inconvenient way
			final List<Element> userNodes = new ArrayList<Element>();
			for (final Element node : (List<Element>)root.getChildren()) {
				//	Determine all the nodes that define a User
				if (node.getName().equalsIgnoreCase("user")) {
					userNodes.add(node);
					getCache().setUser(node.getAttributeValue("id"),
							users.createUser(getChildText(node, "name"),
									theme.getUserType(getChildText(node, "type"))));
				}
			}
			for (final Element userNode : userNodes) {
				final UserImpl user =
					(UserImpl)getCache().getUser(userNode.getAttributeValue("id"));
				
				for (final Element taskNode : getNodes(userNode, "tasks")) {
					final HashMap<String, Field>	fields	=
						new HashMap<String, Field>();
					final TaskType					type	=
						theme.getTaskType(getChildText(taskNode, "type"));
					
					//	Fields
					for (final Element fieldNode : getNodes(taskNode, "fields")) {
						final String	fieldName	=
							theme.getFieldName(type, fieldNode.getAttributeValue("id"));
						final FieldType	fieldType	=
							type.getFieldsTemplate().get(fieldName);
						
						Object value = fieldNode.getAttributeValue("value");
						switch (fieldType) {
							case NUMERICAL:
								value = Integer.parseInt((String)value);
								break;
								
							case TEXTUAL:
								//	ignored
								break;
								
							default:
								throw new InternalError("Invalid FieldType detected");
						}
						
						fields.put(fieldName, new Field(fieldType, value));
					}
					
					//	(create)
					final Schedule schedule = getSchedule(taskNode);
					users.setUser(admin);
					users.adjustClockValue(schedule.getStartDate());
					users.setUser(user);
					final Task task =
						user.createTaskInitial(schedule,
								new HashSet<TaskImpl>(),
								fields,
								(TaskTypeImpl)type);
					getCache().setTask(taskNode.getAttributeValue("id"), task);
					
					//	Project assignment
					projects.assignTaskToProject(task,
							getCache().getProject(getChildText(taskNode, "refProject")));
					
					//	Reservations
					for (final Element node : getNodes(taskNode, "reservations")) {
						// Change system time to the start of this reservation, so no exceptions are thrown
						users.setUser(admin);
						users.adjustClockValue(getDate(getChildText(node, "time")));
						users.setUser(user);
						
						parseReservation(task, node, resources);
					}
				}
			}
			for (final Element userNode : userNodes) {
				final User user =
					getCache().getUser(userNode.getAttributeValue("id"));
				
				for (final Element taskNode : getNodes(userNode, "tasks")) {
					final TaskImpl task =
						(TaskImpl)getCache().getTask(taskNode.getAttributeValue("id"));
					
					users.setUser(admin);
					users.adjustClockValue(task.getSchedule().getStartDate());
					users.setUser(user);
					
					//	Dependencies
					final Set<Task> dependencies = new LinkedHashSet<Task>();
					for (final Element node : getNodes(taskNode, "dependsOn"))
						dependencies.add(getCache().getTask(node.getText()));
					tasks.modifyTask(task,
							task.getSchedule(),
							dependencies,
							task.getFields());
					
					//	Invitations
					for (final Element node : getNodes(taskNode, "invitees"))
						users.addUserInvitation(task,
								getCache().getUser(node.getText()));
				}
			}
			for (final Element userNode : userNodes) {
				for (final Element taskNode : getNodes(userNode, "tasks")) {
					final TaskImpl task =
						(TaskImpl)getCache().getTask(taskNode.getAttributeValue("id"));
					//	Status update
					tasks.updateTaskStatus(task,
							getTaskStatus(getChildText(taskNode, "status")));
				}
			}
			
			
			//	System time update
			users.setUser(admin);
			users.adjustClockValue(time);
//			users.logout();	//	TODO:	Enable after debugging
			
			
			
			
			
			//	TODO:	Remove debugging
			print("===  System time  ===");
			print("    %s", time.getTime());
			int i;
			
			
			print("");
			print("===  Projects  ===");
			i = 0;
			for (final Project project : projects.getProjects()) {
				print("    %d:    %s", i++, project.getDescription());
			}
			
			
			print("");
			print("===  Resources  ===");
			i = 0;
			for (final Resource resource : resources.getResources()) {
				print("    %d:    %-30s  {%s}", i++,
						resource.getDescription(),
						resource.getResourceType().getName());
			}
			
			
			print("");
			print("===  Users & Tasks  ===");
			i = 0;
			for (final User usr : users.getUsers()) {
				final UserImpl user = (UserImpl)usr;
				print("    %d:    %-30s  [%s]", i++,
						user.getName(),
						user.getType().getName());
				
				print("      Tasks:");
				
				int j = 0;
				for (final TaskImpl task : user.getTasks()) {
					print("        %d:    (%-8d)  [%s, %s] {%s}", j++,
							task.hashCode(),
							task.getType().getName(),
							task.getProject().getDescription(),
							task.getStatus());
					
					print("          Dependencies:");
					for (final Task dependency : task.getDependencies())
						print("            %d", dependency.hashCode());
					
					print("          Fields:");
					for (final Entry<String, Field> entry : task.getFields().entrySet()) {
						print("            { %-24s  =  %-24s  (%s) }",
								entry.getKey(),
								entry.getValue().getValue(),
								entry.getValue().getType().name());
					}
					
					print("          Reservations:");
					int k = 0;
					for (final ReservationImpl reservation : task.getReservations()) {
						print("            %d:    %-30s  [%s - %s]", k++,
								reservation.getResource().getDescription(),
								reservation.getDate().getTime(),
								reservation.getEnd().getTime());
					}
					
					print("          Schedule:");
					print("            A:  %s", task.getSchedule().getStartDate().getTime());
					print("            B:  %s", task.getSchedule().getDeadline().getTime());
					print("            T:  %s",
							(task.getSchedule().getDuration() / 60000));
				}
				
				print("      Invitations:");
				for (final TaskImpl task : user.getInvitedTasks())
					print("        %d", task.hashCode());
			}
		} catch (final Exception e) {
			e.printStackTrace();	//	TODO:	Remove after testing
			throw new IOException("Invalid XML data");
		}
	}
	
	
	
	private final void parseProject(final Element node,
			final ProjectController controller) throws Exception {
		getCache().setProject(node.getAttributeValue("id"),
				controller.createProject(getChildText(node, "description")));
	}
	
	private final void parseReservation(final Task task, final Element node,
			final ResourceController resourceController)
			throws TaskManagerException, InvalidCurrentUserException {
		resourceController.makeResourceReservation(task,
				getCache().getResource(getChildText(node, "refResource")),
				getDate(getChildText(node, "time")),
				getDuration(getChildText(node, "duration")));
	}
	
	private final void parseResource(final Element node,
			final ResourceController controller, final Theme theme)
			throws Exception {
		getCache().setResource(node.getAttributeValue("id"),
				controller.createResource(theme.getResourceType(
						getChildText(node, "type")),
						getChildText(node, "description")));
	}
	
	private final Calendar parseSystemTime(final Element node) {
		return getDate(node.getText());
	}
	
	
	
	
	
	
	
	
	
	
	private static class Cache {
		
		private final SortedMap<String, Project>	projects	=
			new TreeMap<String, Project>();
		private final SortedMap<String, Resource>	resources	=
			new TreeMap<String, Resource>();
		private final SortedMap<String, Task>		tasks		=
			new TreeMap<String, Task>();
		private final SortedMap<String, User>		users		=
			new TreeMap<String, User>();
		
		
		
		public Project getProject(final String id) {
			return projects.get(id);
		}
		
		public Resource getResource(final String id) {
			return resources.get(id);
		}
		
		public Task getTask(final String id) {
			return tasks.get(id);
		}
		
		public User getUser(final String id) {
			return users.get(id);
		}
		
		public void setProject(final String id, final Project project) {
			projects.put(id, project);
		}
		
		public void setResource(final String id, final Resource resource) {
			resources.put(id, resource);
		}
		
		public void setTask(final String id, final Task task) {
			tasks.put(id, task);
		}
		
		public void setUser(final String id, final User user) {
			users.put(id, user);
		}
		
	}
	
}